// Copyright 2016 The Draco Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef DRACO_POINT_CLOUD_POINT_CLOUD_H_
#define DRACO_POINT_CLOUD_POINT_CLOUD_H_

#include "draco/draco_features.h"

#include "draco/attributes/point_attribute.h"
#include "draco/metadata/geometry_metadata.h"

namespace draco {

// PointCloud is a collection of n-dimensional points that are described by a
// set of PointAttributes that can represent data such as positions or colors
// of individual points (see point_attribute.h).
class PointCloud {
  public:
    PointCloud();
    virtual ~PointCloud() = default;

    // Returns the number of named attributes of a given type.
    int32_t NumNamedAttributes(GeometryAttribute::Type type) const;

    // Returns attribute id of the first named attribute with a given type or -1
    // when the attribute is not used by the point cloud.
    int32_t GetNamedAttributeId(GeometryAttribute::Type type) const;

    // Returns the id of the i-th named attribute of a given type.
    int32_t GetNamedAttributeId(GeometryAttribute::Type type, int i) const;

    // Returns the first named attribute of a given type or nullptr if the
    // attribute is not used by the point cloud.
    const PointAttribute *GetNamedAttribute(GeometryAttribute::Type type) const;

    // Returns the i-th named attribute of a given type.
    const PointAttribute *GetNamedAttribute(GeometryAttribute::Type type,
                                            int i) const;

    // Returns the named attribute of a given unique id.
    const PointAttribute *GetNamedAttributeByUniqueId(
        GeometryAttribute::Type type, uint32_t id) const;

    // Returns the attribute of a given unique id.
    const PointAttribute *GetAttributeByUniqueId(uint32_t id) const;
    int32_t GetAttributeIdByUniqueId(uint32_t unique_id) const;

    int32_t num_attributes() const {
        return attributes_.size();
    }
    const PointAttribute *attribute(int32_t att_id) const {
        DRACO_DCHECK_LE(0, att_id);
        DRACO_DCHECK_LT(att_id, static_cast<int32_t>(attributes_.size()));
        return attributes_[att_id].get();
    }

    // Returned attribute can be modified, but it's caller's responsibility to
    // maintain the attribute's consistency with draco::PointCloud.
    PointAttribute *attribute(int32_t att_id) {
        DRACO_DCHECK_LE(0, att_id);
        DRACO_DCHECK_LT(att_id, static_cast<int32_t>(attributes_.size()));
        return attributes_[att_id].get();
    }

    // Adds a new attribute to the point cloud.
    // Returns the attribute id.
    int AddAttribute(std::unique_ptr<PointAttribute> pa);

    // Creates and adds a new attribute to the point cloud. The attribute has
    // properties derived from the provided GeometryAttribute |att|.
    // If |identity_mapping| is set to true, the attribute will use identity
    // mapping between point indices and attribute value indices (i.e., each point
    // has a unique attribute value).
    // If |identity_mapping| is false, the mapping between point indices and
    // attribute value indices is set to explicit, and it needs to be initialized
    // manually using the PointAttribute::SetPointMapEntry() method.
    // |num_attribute_values| can be used to specify the number of attribute
    // values that are going to be stored in the newly created attribute.
    // Returns attribute id of the newly created attribute.
    int AddAttribute(const GeometryAttribute &att, bool identity_mapping,
                     AttributeValueIndex::ValueType num_attribute_values);

    // Assigns an attribute id to a given PointAttribute. If an attribute with the
    // same attribute id already exists, it is deleted.
    virtual void SetAttribute(int att_id, std::unique_ptr<PointAttribute> pa);

    // Deletes an attribute with specified attribute id. Note that this changes
    // attribute ids of all subsequent attributes.
    virtual void DeleteAttribute(int att_id);

#ifdef DRACO_ATTRIBUTE_DEDUPLICATION_SUPPORTED
    // Deduplicates all attribute values (all attribute entries with the same
    // value are merged into a single entry).
    virtual bool DeduplicateAttributeValues();

    // Removes duplicate point ids (two point ids are duplicate when all of their
    // attributes are mapped to the same entry ids).
    virtual void DeduplicatePointIds();
#endif

    // Add metadata.
    void AddMetadata(std::unique_ptr<GeometryMetadata> metadata) {
        metadata_ = std::move(metadata);
    }

    // Add metadata for an attribute.
    void AddAttributeMetadata(int32_t att_id,
                              std::unique_ptr<AttributeMetadata> metadata) {
        if (!metadata_.get()) {
            metadata_ = std::unique_ptr<GeometryMetadata>(new GeometryMetadata());
        }
        const int32_t att_unique_id = attribute(att_id)->unique_id();
        metadata->set_att_unique_id(att_unique_id);
        metadata_->AddAttributeMetadata(std::move(metadata));
    }

    const AttributeMetadata *GetAttributeMetadataByAttributeId(
        int32_t att_id) const {
        if (metadata_ == nullptr)
            return nullptr;
        const uint32_t unique_id = attribute(att_id)->unique_id();
        return metadata_->GetAttributeMetadataByUniqueId(unique_id);
    }

    // Returns the attribute metadata that has the requested metadata entry.
    const AttributeMetadata *GetAttributeMetadataByStringEntry(
        const std::string &name, const std::string &value) const {
        if (metadata_ == nullptr)
            return nullptr;
        return metadata_->GetAttributeMetadataByStringEntry(name, value);
    }

    // Returns the first attribute that has the requested metadata entry.
    int GetAttributeIdByMetadataEntry(const std::string &name,
                                      const std::string &value) const {
        if (metadata_ == nullptr)
            return -1;
        const AttributeMetadata *att_metadata =
            metadata_->GetAttributeMetadataByStringEntry(name, value);
        if (!att_metadata)
            return -1;
        return GetAttributeIdByUniqueId(att_metadata->att_unique_id());
    }

    // Get a const pointer of the metadata of the point cloud.
    const GeometryMetadata *GetMetadata() const {
        return metadata_.get();
    }

    // Get a pointer to the metadata of the point cloud.
    GeometryMetadata *metadata() {
        return metadata_.get();
    }

    // Returns the number of n-dimensional points stored within the point cloud.
    PointIndex::ValueType num_points() const {
        return num_points_;
    }

    // Sets the number of points. It's the caller's responsibility to ensure the
    // new number is valid with respect to the PointAttributes stored in the point
    // cloud.
    void set_num_points(PointIndex::ValueType num) {
        num_points_ = num;
    }

  protected:
#ifdef DRACO_ATTRIBUTE_DEDUPLICATION_SUPPORTED
    // Applies id mapping of deduplicated points (called by DeduplicatePointIds).
    virtual void ApplyPointIdDeduplication(
        const IndexTypeVector<PointIndex, PointIndex> &id_map,
        const std::vector<PointIndex> &unique_point_ids);
#endif

  private:
    // Metadata for the point cloud.
    std::unique_ptr<GeometryMetadata> metadata_;

    // Attributes describing the point cloud.
    std::vector<std::unique_ptr<PointAttribute>> attributes_;

    // Ids of named attributes of the given type.
    std::vector<int32_t>
    named_attribute_index_[GeometryAttribute::NAMED_ATTRIBUTES_COUNT];

    // The number of n-dimensional points. All point attribute values are stored
    // in corresponding PointAttribute instances in the |attributes_| array.
    PointIndex::ValueType num_points_;

    friend struct PointCloudHasher;
};

// Functor for computing a hash from data stored within a point cloud.
// Note that this can be quite slow. Two point clouds will have the same hash
// only when all points have the same order and when all attribute values are
// exactly the same.
struct PointCloudHasher {
    size_t operator()(const PointCloud &pc) const {
        size_t hash = pc.num_points_;
        hash = HashCombine(pc.attributes_.size(), hash);
        for (int i = 0; i < GeometryAttribute::NAMED_ATTRIBUTES_COUNT; ++i) {
            hash = HashCombine(pc.named_attribute_index_[i].size(), hash);
            for (int j = 0; j < static_cast<int>(pc.named_attribute_index_[i].size());
                    ++j) {
                hash = HashCombine(pc.named_attribute_index_[i][j], hash);
            }
        }
        // Hash attributes.
        for (int i = 0; i < static_cast<int>(pc.attributes_.size()); ++i) {
            PointAttributeHasher att_hasher;
            hash = HashCombine(att_hasher(*pc.attributes_[i]), hash);
        }
        // Hash metadata.
        GeometryMetadataHasher metadata_hasher;
        if (pc.metadata_.get()) {
            hash = HashCombine(metadata_hasher(*pc.metadata_.get()), hash);
        }
        return hash;
    }
};

}  // namespace draco

#endif  // DRACO_POINT_CLOUD_POINT_CLOUD_H_
